package com.rainbow.idempotent;

import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import com.rainbow.idempotent.enums.IdempotentStorageTypeEnum;
import com.rainbow.idempotent.lock.DistributedLock;
import com.rainbow.idempotent.request.IdempotentRequest;
import com.rainbow.idempotent.storage.IdempotentStorage;
import com.rainbow.idempotent.storage.IdempotentStorageFactory;

import lombok.extern.slf4j.Slf4j;

/**
 * 幂等实现
 *
 */
@Slf4j
public class DistributedIdempotentImpl implements DistributedIdempotent {

    @Autowired
    private DistributedLock distributedLock;

    @Autowired
    private IdempotentStorageFactory idempotentStorageFactory;

    /**
     * 锁名称后缀，区分锁和幂等的Key
     */
    private String lockSuffix = "_lock";

    /**
     * 幂等Key对应的默认值
     */
    private String idempotentDefaultValue = "1";

    @Override
    public <T> T execute(String key, int lockExpireTime, int storeExpireTime,TimeUnit timeUnit, IdempotentStorageTypeEnum  idempotentStorageTypeEnum, IdempotentStorage idempotentStorageCustom ,Supplier<T> execute, Supplier<T> fail) {
        IdempotentRequest idempotentRequest = IdempotentRequest.builder().key(key)
                .lockExpireTime(lockExpireTime)
                .storeExpireTime(storeExpireTime)
                .timeUnit(timeUnit)
                .idempotentStorageTypeEnum(idempotentStorageTypeEnum)
                .idempotentStorageCustom(idempotentStorageCustom)
                .build();
        return execute(idempotentRequest, execute, fail);
    }
    
    @Override
    public <T> T execute(String key, int lockExpireTime, int storeExpireTime,TimeUnit timeUnit, IdempotentStorageTypeEnum  idempotentStorageTypeEnum, Supplier<T> execute, Supplier<T> fail) {
        IdempotentRequest idempotentRequest = IdempotentRequest.builder().key(key)
                .lockExpireTime(lockExpireTime)
                .storeExpireTime(storeExpireTime)
                .timeUnit(timeUnit)
                .idempotentStorageTypeEnum(idempotentStorageTypeEnum)
                .idempotentStorageCustom(null)
                .build();
        return execute(idempotentRequest, execute, fail);
    }
    
    
    @SuppressWarnings("unchecked")
	private <T> T doExecute(IdempotentRequest request, Object execute, Object fail) {
    	if (request.getIdempotentStorageTypeEnum() != IdempotentStorageTypeEnum.CUSTOM) {
    		IdempotentStorage idempotentStorage = idempotentStorageFactory.getIdempotentStorage(request.getIdempotentStorageTypeEnum());
    		if (idempotentStorage == null) {
    			log.error("idempotentStorage的存储实现为null typeEnum:" + request.getIdempotentStorageTypeEnum().name());
			}else {
				log.info("idempotentStorage的存储实现方式:" + idempotentStorage.type().name());
	    		String value = idempotentStorage.getValue(request.getKey());
	    		if (!StringUtils.hasText(value)) {
	    			T executeResult = getExecuteResult(execute);
	    			idempotentStorage.setValue(request.getKey(), idempotentDefaultValue, request.getStoreExpireTime(), request.getTimeUnit());
	    			return executeResult;
	    		}
			}
		}else {
			IdempotentStorage idempotentStorage = request.getIdempotentStorageCustom();
			if (idempotentStorage == null) {
    			log.error("idempotentStorage的自定义存储实现为null typeEnum:" + request.getIdempotentStorageTypeEnum().name());
			}else {
				log.info("idempotentStorage的存储实现方式:" + idempotentStorage.type().name());
	    		String value = idempotentStorage.getValue(request.getKey());
	    		if (!StringUtils.hasText(value)) {
	    			T executeResult = getExecuteResult(execute);
	    			idempotentStorage.setValue(request.getKey(), idempotentDefaultValue, request.getStoreExpireTime(), request.getTimeUnit());
	    			return executeResult;
	    		}
			}
		}
    	
    	// 不能继续往下执行
        if (execute instanceof Supplier) {
            Supplier<T> failSupplier = (Supplier<T>) fail;
            return failSupplier.get();
        } else {
            Runnable failRunnable = (Runnable) execute;
            failRunnable.run();
            return null;
        }
    }


    @SuppressWarnings("unchecked")
	private <T> T getExecuteResult(Object execute) {
        T executeResult = null;
        if (execute instanceof Supplier) {
            Supplier<T> executeSupplier = (Supplier<T>) execute;
            executeResult = executeSupplier.get();
        } else {
            Runnable executeRunnable = (Runnable) execute;
            executeRunnable.run();
        }
        return executeResult;
    }


    @Override
    public <T> T execute(IdempotentRequest request, Supplier<T> execute, Supplier<T> fail) {
        return distributedLock.lock(request.getKey() + lockSuffix, request.getLockExpireTime(), request.getTimeUnit(), () -> {
            try {
            	return doExecute(request, execute, fail);
			} catch (Exception e) {
				return fail.get();
			}
        }, fail);
    }

    @Override
    public void execute(String key, int lockExpireTime, int storeExpireTime,TimeUnit timeUnit, IdempotentStorageTypeEnum idempotentStorageTypeEnum, IdempotentStorage idempotentStorageCustom ,Runnable execute, Runnable fail) {
        IdempotentRequest idempotentRequest = IdempotentRequest.builder().key(key)
                .lockExpireTime(lockExpireTime)
                .timeUnit(timeUnit)
                .idempotentStorageTypeEnum(idempotentStorageTypeEnum)
                .idempotentStorageCustom(idempotentStorageCustom)
                .build();
        execute(idempotentRequest, execute, fail);
    }
    
    @Override
    public void execute(String key, int lockExpireTime, int storeExpireTime,TimeUnit timeUnit, IdempotentStorageTypeEnum idempotentStorageTypeEnum, Runnable execute, Runnable fail) {
        IdempotentRequest idempotentRequest = IdempotentRequest.builder().key(key)
                .lockExpireTime(lockExpireTime)
                .timeUnit(timeUnit)
                .idempotentStorageTypeEnum(idempotentStorageTypeEnum)
                .idempotentStorageCustom(null)
                .build();
        execute(idempotentRequest, execute, fail);
    }

    @Override
    public void execute(IdempotentRequest request, Runnable execute, Runnable fail) {
        distributedLock.lock(request.getKey() + lockSuffix, request.getLockExpireTime(), request.getTimeUnit(), () -> {
            try {
            	doExecute(request, execute, fail);
			} catch (Exception e) {
				fail.run();
			}
        }, fail);
    }
}